<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <title>FX Client: recent prices (xhr)</title>
  <link rel="stylesheet" type="text/css" href="fx_client.css" />
  <script>var isIE9OrEarlier = false;</script>
  <!--[if lte IE 7]>
    <script src="json2.min.js"></script>
    <![endif]-->
  <!--[if lte IE 9]>
  <script language="javascript">
    isIE9OrEarlier = true;
  </script>
  <![endif]-->
  <script>
    Object.keys=Object.keys || function(o,k,r){
      r=[];
      for(k in o)if(o.hasOwnProperty(k))r.push(k);
      return r;
    }
  </script>
</head>
<body>
<div id="msg"></div>
<table class="latest-price-table">
  <tr><th>USD/JPY</th><th>EUR/USD</th><th>AUD/GBP</th></tr>
  <tr><td id="USD/JPY"></td><td id="EUR/USD"></td><td id="AUD/GBP"></td></tr>
</table>
<br clear="all" />

<table class="price-table">
  <caption>USD/JPY</caption>
  <thead>
  <tr>
    <th>Timestamp</th>
    <th>Bid</th>
    <th>Ask</th>
  </tr>
  </thead>
  <tbody id="history_USD/JPY"></tbody>
</table>

<table class="price-table">
  <caption>EUR/USD</caption>
  <thead>
  <tr>
    <th>Timestamp</th>
    <th>Bid</th>
    <th>Ask</th>
  </tr>
  </thead>
  <tbody id="history_EUR/USD"></tbody>
</table>

<table class="price-table">
  <caption>AUD/GBP</caption>
  <thead>
  <tr>
    <th>Timestamp</th>
    <th>Bid</th>
    <th>Ask</th>
  </tr>
  </thead>
  <tbody id="history_AUD/GBP"></tbody>
</table>

<script>
  var url = "03.fx_server.compatible.php?";

  function SSE(url,options){
    if(!options)options={};
    var defaultOptions={
      keepaliveSecs: 20
    };
    for(var key in defaultOptions)
      if(!options.hasOwnProperty(key))
        options[key]=defaultOptions[key];

    var es = null, xhr = null, iframe = null;
    var iframeTimer = null;
    var fullHistory = {};
    var keepaliveTimer = null;
    var lastId = null;
    var longPollTimer = null;

    function gotActivity(){
      if(keepaliveTimer!=null)clearTimeout(keepaliveTimer);
      keepaliveTimer = setTimeout(connect,options.keepaliveSecs * 1000);
    }

    function startEventSource(){
      if(es)es.close();
      var u = url;
      if(lastId)u += "lastId="
        + encodeURIComponent(lastId) + '&';
      es = new EventSource(u);
      es.addEventListener('message', function(e){processOneLine(e.data);},false);
      es.addEventListener('error', handleError, false);
    }

    function handleError(e){console.log(e);}

    function startLongPoll(){
      options.keepaliveSecs = 300;
      if(xhr)xhr.abort();
      if(window.XMLHttpRequest)xhr = new XMLHttpRequest();
      else xhr = new ActiveXObject("Msxml2.XMLHTTP");
      xhr.onreadystatechange = longPollOnReadyStateChange;
      var u = url;
      u += "longpoll=1&t=" + (new Date().getTime());
      xhr.open('GET',u);
      if(lastId)xhr.setRequestHeader('Last-Event-ID',lastId)
      xhr.send(null);
    }

    function longPollOnReadyStateChange(){
      if(this.readyState != 4)return;
      xhr = null;
      if(this.status == 200){
        longPollTimer = setTimeout(startLongPoll, 50);
        processNonSSE(this.responseText);
      }
      else{
        console.log("Connection failure, status:"+this.status);
        disconnect();
        longPollTimer = setTimeout(startLongPoll, 30000);
      }
    }

    function getNewText(s,prevOffset){
      var lastLF = s.lastIndexOf("\n")+1;
      if(lastLF == 0 || prevOffset == lastLF)return prevOffset;
      var lines = s.substring(prevOffset, lastLF - 1).split(/\n/);
      for(var ix in lines)processNonSSE(lines[ix]);

      if(lastLF > 65536){
        console.log("Received "+lastLF+" bytes ("+s.length+"). Will reconnect.");
        disconnect();
        setTimeout(connect,1);
      }

      return lastLF; //Starting point for next time
    }

    function startXHR(){
      if(xhr)xhr.abort();
      xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function(){
        this.prevOffset = getNewText(
          this.responseText,this.prevOffset);
      };
      xhr.prevOffset = 0;
      var u = url;
      u += "xhr=1&t=" + (new Date().getTime());
      xhr.open('GET',u);
      if(lastId)xhr.setRequestHeader('Last-Event-ID',lastId)
      xhr.send(null);
    }

    function startIframe(){
      if(iframe)iframe.parentNode.removeChild(iframe);
      if(iframeTimer)clearInterval(iframeTimer);
      var u = url;
      if(lastId)u += "lastId=" + encodeURIComponent(lastId) + '&';
      u += "xhr=1&t=" + (new Date().getTime());
      iframe = document.createElement("iframe");
      iframe.setAttribute("style", "display: none;");
      iframe.setAttribute("src", u);
      document.body.appendChild(iframe);
      var prevOffset = 0;
      iframeTimer = setInterval(function(){
        if(!iframe.contentWindow.document.body)return;
        var s = iframe.contentWindow.document.body.innerHTML;
        prevOffset = getNewText(s, prevOffset);
      }, 500);
    }


    function connect(){
      //if(true)startLongPoll();else
      //if(true)startXHR();else
      //if(true)startIframe();else

      if(window.EventSource)startEventSource();
      else if(isIE9OrEarlier){
        if(window.postMessage)startIframe();
        else startLongPoll();
      }
      else if(window.XMLHttpRequest &&
        typeof new XMLHttpRequest().responseType != "undefined")
        startXHR();
      else startLongPoll();
      gotActivity();
    }

    function disconnect(){
      if(keepaliveTimer){
        clearTimeout(keepaliveTimer);
        keepaliveTimer = null;
      }
      if(es){
        es.close();
        es = null;
      }
      if(xhr){
        xhr.abort();
        xhr = null;
      }
      if(longPollTimer){
        clearTimeout(longPollTimer);
        longPollTimer = null;
      }
      if(iframeTimer){
        clearTimeout(iframeTimer);
        iframeTimer = null;
      }
      if(iframe){
        iframe.parentNode.removeChild(iframe);
        iframe = null;
      }
    }

    function processNonSSE(msg){
      var lines = msg.split(/\n/);
      for(var ix in lines){
        var s = lines[ix];
        if(s.length == 0)continue;
        if(s[0] != '{'){
          s = s.substring(s.indexOf("{"));
          if(s.length == 0)continue;
        }
        processOneLine(s);
      }
    }

    function processOneLine(s){
      gotActivity();
      try{
        var d = JSON.parse(s);
      }catch(e){
        console.log("BAD JSON:"+s+"\n"+e);
        return;
      }

      if(d.seed){
        var x = document.getElementById('msg');
        x.innerHTML += "seed="+d.seed+"<br/>";
      }
      else if(d.symbol){
        if(!fullHistory.hasOwnProperty(d.symbol))fullHistory[d.symbol] = {};
        var x = document.getElementById(d.symbol);
        for(var ix in d.rows){
          var r = d.rows[ix];
          x.innerHTML = d.rows[ix].bid;
          fullHistory[d.symbol][r.timestamp] = [r.bid,r.ask];
          lastId = r.id;
        }
        updateHistoryTable(d.symbol);
      }
      else if(d.action=="keep-alive"){
        document.getElementById("msg").innerHTML+=
          "Keep-alive:"+d.timestamp+"<br/>";
      }
      else if(d.action=="scheduled_shutdown"){
        document.getElementById("msg").innerHTML+=
          "Scheduled shutdown from now. Come back at "+
          d.until+"(in "+d.until_secs+" secs).<br/>";
        temporarilyDisconnect(d.until_secs);
      }
    }

    function temporarilyDisconnect(secs){
      var millisecs = secs * 1000;
      millisecs -= Math.random() * 60000;
      if(millisecs < 0)return;
      disconnect();
      setTimeout(connect,millisecs);
    }

    function updateHistoryTable(symbol){
      var tbody = makeHistoryTbody(fullHistory[symbol]);
      var x = document.getElementById("history_" + symbol);
      x.parentNode.replaceChild(tbody, x);
      tbody.id = x.id;
    }

    /**
     * @param history An object, keyed on timestamp strings
     * @return An HTML tbody
     */
    function makeHistoryTbody(history){
      var tbody = document.createElement('tbody');
      var keys = Object.keys(history).sort().slice(-10).reverse();

      var timestamp, v, row, cell;
      for(var n = 0;n < keys.length;n++){
        timestamp = keys[n];
        v = history[timestamp];
        row = document.createElement('tr');

        cell = document.createElement('th');
        cell.appendChild(document.createTextNode(timestamp));
        row.appendChild(cell);

        cell = document.createElement('td');
        cell.appendChild(document.createTextNode(v[0]));
        row.appendChild(cell);

        cell = document.createElement('td');
        cell.appendChild(document.createTextNode(v[1]));
        row.appendChild(cell);

        tbody.appendChild(row);
      }
      return tbody;
    }

    connect();
  }

  setTimeout(function(){new SSE(url);}, 100);
</script>
</body>
</html>